feat: Add inline editing for tables on desktop (V2)#89166
feat: Add inline editing for tables on desktop (V2)#89166mohammadjafarinejad wants to merge 47 commits into
Conversation
This reverts commit 78be79e. Co-authored-by: Copilot <copilot@github.com>
|
Hey! I see that you made changes to our Form component. Make sure to update the docs in FORMS.md accordingly. Cheers! |
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
Codecov Report✅ Changes either increased or maintained existing code coverage, great job!
|
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
|
@mohammadjafarinejad when can you have this ready for review? |
|
And let us know when we can adhoc test build 😄 |
|
Bump @mohammadjafarinejad. Can we get daily updates please to get this back out? |
|
Bump again @mohammadjafarinejad. Please provide an update. |
|
@trjExpensify I'll have it ready for review by tomorrow, after I test it to make sure all bugs are fixed. |
|
Okay, thank you. |
|
@ahmedGaber93 Ready for re-review. |
|
@ahmedGaber93 for now I think we should allow the
I agree this could be a possible alternative, but it's implicit logic that isn't super obvious, so I think let's reserve it for a follow-up if necessary. |
ahmedGaber93
left a comment
There was a problem hiding this comment.
Just two comments, then we can move forward
| hash, | ||
| transactionID, | ||
| parentReport: parentReport ?? fallbackReport, | ||
| transactionThreadReport: transactionThreadReport ?? parentReport ?? fallbackReport, |
There was a problem hiding this comment.
@mohammadjafarinejad Passing parentReport as a fallback here is causing an issue for me. When editing a cell from Spend > Expenses, a system message is added to both the expense report and the transaction report.
I think we should avoid falling back to parentReport here.
const transactionThreadReportFallback: OnyxEntry<Report> = transactionThreadReportID ? ({reportID: transactionThreadReportID} as Report) : undefined;and pass it into getEditParams and getTransactionEditPermissions
transactionThreadReport: transactionThreadReport ?? transactionThreadReportFallback,Screen.Recording.2026-05-07.at.1.31.13.AM.mov
There was a problem hiding this comment.
@mohammadjafarinejad I removed the suggested fix on the comment above. Although it works since the correct reportID is being passed, the missing attributes on the report could lead to unexpected issues and it feels like a fragile solution.
What cases cause this report to be missing? And what would happen if we don’t provide a fallback at all?
There was a problem hiding this comment.
@ahmedGaber93 We need transactionThreadReport to update the report actions, but sometimes, especially for unreported expenses, it's undefined, which I think is related to this bug too:
I was able to solve it using the changes below. We already have similar logic in BulkEdit, where we resolve missing context for unreported expenses and recreate the transaction thread when needed. WDYT about this approach?
Diff Patch
diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionItem.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionItem.tsx
index 6ed8a8ce984..8f16707f6c0 100644
--- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionItem.tsx
+++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionItem.tsx
@@ -129,7 +129,7 @@ function MoneyRequestReportTransactionItem({
onEditAmount,
onEditTag,
wasEditingOnMouseDownRef,
- } = useTransactionInlineEdit({transactionID: transaction.transactionID, reportID: transaction.reportID});
+ } = useTransactionInlineEdit({transactionID: transaction.transactionID});
const viewRef = useRef<View>(null);
diff --git a/src/components/Search/SearchList/ListItem/TransactionListItem.tsx b/src/components/Search/SearchList/ListItem/TransactionListItem.tsx
index 3483e2075d7..39cbfca4f22 100644
--- a/src/components/Search/SearchList/ListItem/TransactionListItem.tsx
+++ b/src/components/Search/SearchList/ListItem/TransactionListItem.tsx
@@ -175,8 +175,6 @@ function TransactionListItem<TItem extends ListItem>({
const {isDelegateAccessRestricted} = useDelegateNoAccessState();
const {showDelegateNoAccessModal} = useDelegateNoAccessActions();
- const transactionID = transactionItem.transactionID;
-
const {isEditingCell, wasRecentlyEditingCell} = useEditingCellState();
const [shouldDisableHoverStyle, setShouldDisableHoverStyle] = useState(false);
@@ -205,12 +203,9 @@ function TransactionListItem<TItem extends ListItem>({
onEditTag,
wasEditingOnMouseDownRef,
} = useTransactionInlineEdit({
- transactionID,
- reportID: transactionItem.reportID,
- reportActionID: transactionItem.reportAction?.reportActionID,
- parentReportAction,
+ transactionID: transactionItem.transactionID,
hash: currentSearchHash,
- fallbackReport: snapshotReport,
+ linkedReportAction: transactionItem.reportAction,
});
const handleOnPress = () => {
diff --git a/src/hooks/useTransactionInlineEdit.ts b/src/hooks/useTransactionInlineEdit.ts
index 508d01f3972..734d186cfc5 100644
--- a/src/hooks/useTransactionInlineEdit.ts
+++ b/src/hooks/useTransactionInlineEdit.ts
@@ -4,7 +4,7 @@
* than being duplicated across every surface that renders a transaction.
*/
import {useCallback, useRef} from 'react';
-// eslint-disable-next-line no-restricted-imports -- Need original useOnyx to avoid reading partial Search snapshot policy data.
+// eslint-disable-next-line no-restricted-imports -- Need original useOnyx to avoid reading partial Search snapshot policy data
import {useOnyx as originalUseOnyx} from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx';
import {useSearchStateContext} from '@components/Search/SearchContext';
@@ -23,28 +23,14 @@ import {getIOUActionForTransactionID} from '@libs/ReportActionsUtils';
import {isExpenseUnreported, isPerDiemRequest} from '@libs/TransactionUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
-import type {Report, ReportAction, ReportActions} from '@src/types/onyx';
+import type {ReportAction, ReportActions} from '@src/types/onyx';
import useOnyx from './useOnyx';
import usePolicyForMovingExpenses from './usePolicyForMovingExpenses';
import usePolicyForTransaction from './usePolicyForTransaction';
+import useSelfDMReport from './useSelfDMReport';
type UseTransactionInlineEditParams = {
transactionID: string;
- reportID: string | undefined;
-
- /**
- * When provided the action is looked up directly by ID (faster).
- * If omitted, the hook scans all actions for the one matching this transaction.
- * Ignored when `parentReportAction` is supplied directly.
- */
- reportActionID?: string;
-
- /**
- * Pre-fetched parent report action.
- * When provided the hook skips its own Onyx subscription entirely, avoiding
- * a duplicate subscription in components that already hold this data.
- */
- parentReportAction?: OnyxEntry<ReportAction>;
/**
* Search snapshot hash.
@@ -54,11 +40,11 @@ type UseTransactionInlineEditParams = {
hash?: number;
/**
- * Fallback report from the search snapshot.
- * Used when the Onyx report cache is empty (e.g. after cache clear) so that
- * permission checks like isSettled still have a report object with statusNum.
+ * Lightweight report action hint from the current surface.
+ * Search rows already have this in snapshot data, which lets the hook avoid
+ * scanning all report actions just to recover the thread/report action IDs.
*/
- fallbackReport?: OnyxEntry<Report>;
+ linkedReportAction?: OnyxEntry<ReportAction>;
};
type UseTransactionInlineEditReturn = {
@@ -82,39 +68,37 @@ type UseTransactionInlineEditReturn = {
wasEditingOnMouseDownRef: React.RefObject<boolean>;
};
-function useTransactionInlineEdit({
- transactionID,
- reportID,
- reportActionID,
- parentReportAction: externalParentReportAction,
- hash,
- fallbackReport,
-}: UseTransactionInlineEditParams): UseTransactionInlineEditReturn {
- // Look up the parent IOU report action from live Onyx. If the caller already
- // knows the action ID we can select it directly; otherwise we scan all actions.
- // When the caller supplies `parentReportAction` directly we still must call
- // useOnyx (rules of hooks) but we ignore its result and prefer the external value.
+function useTransactionInlineEdit({transactionID, hash, linkedReportAction}: UseTransactionInlineEditParams): UseTransactionInlineEditReturn {
+ const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`);
+
+ const reportID = transaction?.reportID;
+ const isUnreported = isExpenseUnreported(transaction);
+ const selfDMReport = useSelfDMReport();
+
+ const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(reportID)}`);
+ const effectiveParentReport = isUnreported ? selfDMReport : parentReport;
+ const effectiveParentReportID = effectiveParentReport?.reportID;
+
+ const linkedReportActionID = linkedReportAction?.reportActionID;
+
const parentReportActionSelector = useCallback(
- (reportActions: ReportActions | undefined) => (reportActionID ? reportActions?.[reportActionID] : getIOUActionForTransactionID(Object.values(reportActions ?? {}), transactionID)),
- [reportActionID, transactionID],
+ (reportActions: ReportActions | undefined) =>
+ linkedReportActionID ? reportActions?.[linkedReportActionID] : getIOUActionForTransactionID(Object.values(reportActions ?? {}), transactionID),
+ [linkedReportActionID, transactionID],
);
-
- const [internalParentReportAction] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getNonEmptyStringOnyxID(reportID)}`, {
+ const [resolvedParentReportAction] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getNonEmptyStringOnyxID(effectiveParentReportID)}`, {
selector: parentReportActionSelector,
});
+ const parentReportAction = resolvedParentReportAction ?? linkedReportAction;
- const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`);
- const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(reportID)}`);
-
- const parentReportAction = externalParentReportAction !== undefined ? externalParentReportAction : internalParentReportAction;
+ const transactionThreadReportID = linkedReportAction?.childReportID ?? parentReportAction?.childReportID ?? transaction?.transactionThreadReportID;
- const transactionThreadReportID = parentReportAction?.childReportID;
- const chatReportID = parentReport?.chatReportID;
+ const chatReportID = effectiveParentReport?.chatReportID;
// For unreported expenses (SelfDM), use active policy to show policy-specific fields like categories and tags.
const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID);
- const reportPolicyID = parentReport?.policyID;
- const policyID = isExpenseUnreported(transaction) ? activePolicyID : reportPolicyID;
+ const reportPolicyID = effectiveParentReport?.policyID;
+ const policyID = isUnreported ? activePolicyID : reportPolicyID;
const {policy} = usePolicyForTransaction({
transaction,
@@ -131,7 +115,7 @@ function useTransactionInlineEdit({
const [chatReportNVP] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${getNonEmptyStringOnyxID(chatReportID)}`);
const [policyRecentlyUsedCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES}${getNonEmptyStringOnyxID(policyID)}`);
const [policyRecentlyUsedTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${getNonEmptyStringOnyxID(policyID)}`);
- const [parentReportNextStep] = useOnyx(`${ONYXKEYS.COLLECTION.NEXT_STEP}${getNonEmptyStringOnyxID(reportID)}`);
+ const [parentReportNextStep] = useOnyx(`${ONYXKEYS.COLLECTION.NEXT_STEP}${getNonEmptyStringOnyxID(effectiveParentReportID)}`);
// Use original Onyx here because the useOnyx wrapper can read partial Search snapshot policy data instead of the full policy object.
const [completePolicy] = originalUseOnyx(`${ONYXKEYS.COLLECTION.POLICY}${getNonEmptyStringOnyxID(policyID)}`);
@@ -146,9 +130,9 @@ function useTransactionInlineEdit({
const permissions = getTransactionEditPermissions({
transaction,
parentReportAction,
- parentReport: parentReport ?? fallbackReport,
+ parentReport: effectiveParentReport,
policy: completePolicy ?? policy,
- transactionThreadReport: transactionThreadReport ?? parentReport ?? fallbackReport,
+ transactionThreadReport,
policyCategories,
policyTags,
transactionThreadNVP,
@@ -164,8 +148,9 @@ function useTransactionInlineEdit({
return {
hash,
transactionID,
- parentReport: parentReport ?? fallbackReport,
- transactionThreadReport: transactionThreadReport ?? parentReport ?? fallbackReport,
+ parentReport: effectiveParentReport,
+ parentReportAction,
+ transactionThreadReport,
policy: completePolicy ?? policy,
policyCategories,
policyTags,
diff --git a/src/libs/actions/TransactionInlineEdit.ts b/src/libs/actions/TransactionInlineEdit.ts
index 9537b8dd4a5..5cceae03eb2 100644
--- a/src/libs/actions/TransactionInlineEdit.ts
+++ b/src/libs/actions/TransactionInlineEdit.ts
@@ -14,11 +14,12 @@ import {getIsOffline} from '@libs/NetworkState';
import {hasEnabledOptions} from '@libs/OptionsListUtils';
import Permissions from '@libs/Permissions';
import {getTagLists, isMultiLevelTags} from '@libs/PolicyUtils';
-import {isMoneyRequestAction} from '@libs/ReportActionsUtils';
+import {getIOUActionForTransactionID, isMoneyRequestAction} from '@libs/ReportActionsUtils';
import {
canEditFieldOfMoneyRequest,
canEditMoneyRequest,
canUserPerformWriteAction,
+ findSelfDMReportID,
isArchivedReport,
isInvoiceReport,
isIOUReport,
@@ -31,6 +32,7 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {
Beta,
+ IntroSelected,
Policy,
PolicyCategories,
PolicyTagLists,
@@ -38,6 +40,7 @@ import type {
RecentlyUsedTags,
Report,
ReportAction,
+ ReportActions,
ReportNameValuePairs,
ReportNextStepDeprecated,
Transaction,
@@ -51,6 +54,7 @@ import {
updateMoneyRequestMerchant,
updateMoneyRequestTag,
} from './IOU/UpdateMoneyRequest';
+import {createTransactionThreadReport} from './Report';
type TransactionEditPermissions = {
canEditDate: boolean;
@@ -79,6 +83,24 @@ Onyx.connectWithoutView({
},
});
+let allReports: NonNullable<OnyxCollection<Report>> = {};
+Onyx.connectWithoutView({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (value) => {
+ allReports = value ?? {};
+ },
+});
+
+let allReportActions: NonNullable<OnyxCollection<ReportActions>> = {};
+Onyx.connectWithoutView({
+ key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
+ waitForCollectionCallback: true,
+ callback: (value) => {
+ allReportActions = value ?? {};
+ },
+});
+
let currentUserAccountID: number = CONST.DEFAULT_NUMBER_ID;
let currentUserEmail = '';
Onyx.connectWithoutView({
@@ -97,6 +119,14 @@ Onyx.connectWithoutView({
},
});
+let introSelected: OnyxEntry<IntroSelected>;
+Onyx.connectWithoutView({
+ key: ONYXKEYS.NVP_INTRO_SELECTED,
+ callback: (value) => {
+ introSelected = value;
+ },
+});
+
const NO_EDIT: Readonly<TransactionEditPermissions> = Object.freeze({
canEditDate: false,
canEditMerchant: false,
@@ -137,6 +167,7 @@ type TransactionEditPermissionsParams = {
type GetIouParamsInput = {
transactionID: string;
parentReport: OnyxEntry<Report>;
+ parentReportAction: OnyxEntry<ReportAction>;
transactionThreadReport: OnyxEntry<Report>;
policy: OnyxEntry<Policy>;
policyCategories: OnyxEntry<PolicyCategories>;
@@ -160,6 +191,7 @@ type TransactionInlineEditParams = GetIouParamsInput & {
function getIouParamsForTransaction({
transactionID,
parentReport,
+ parentReportAction,
transactionThreadReport,
policy,
policyCategories,
@@ -169,12 +201,56 @@ function getIouParamsForTransaction({
parentReportNextStep,
}: GetIouParamsInput) {
const transaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`];
+ const transactionViolations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`];
+ const isUnreportedExpense = !transaction?.reportID || transaction.reportID === CONST.REPORT.UNREPORTED_REPORT_ID;
+
+ let resolvedParentReport = parentReport;
+ if (!resolvedParentReport?.reportID && transaction?.reportID && transaction.reportID !== CONST.REPORT.UNREPORTED_REPORT_ID) {
+ resolvedParentReport = allReports[`${ONYXKEYS.COLLECTION.REPORT}${transaction.reportID}`];
+ }
+
+ let resolvedParentReportAction = parentReportAction;
+ if (!resolvedParentReportAction && resolvedParentReport?.reportID) {
+ const reportActions = allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${resolvedParentReport.reportID}`] ?? {};
+ resolvedParentReportAction = getIOUActionForTransactionID(Object.values(reportActions), transactionID);
+ }
+
+ if (isUnreportedExpense) {
+ const selfDMReportID = findSelfDMReportID(allReports);
+ if (selfDMReportID) {
+ resolvedParentReport = allReports[`${ONYXKEYS.COLLECTION.REPORT}${selfDMReportID}`] ?? resolvedParentReport;
+
+ if (!resolvedParentReportAction) {
+ const selfDMReportActions = allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${selfDMReportID}`] ?? {};
+ resolvedParentReportAction = getIOUActionForTransactionID(Object.values(selfDMReportActions), transactionID);
+ }
+ }
+ }
+
+ let resolvedTransactionThreadReport = transactionThreadReport;
+ const transactionThreadReportID = resolvedTransactionThreadReport?.reportID ?? transaction?.transactionThreadReportID ?? resolvedParentReportAction?.childReportID;
+
+ if (!resolvedTransactionThreadReport && transactionThreadReportID) {
+ resolvedTransactionThreadReport = allReports[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`];
+ }
+
+ if (!resolvedTransactionThreadReport && resolvedParentReportAction && transaction) {
+ resolvedTransactionThreadReport = createTransactionThreadReport(
+ introSelected,
+ currentUserEmail,
+ currentUserAccountID,
+ allBetas,
+ resolvedParentReport,
+ resolvedParentReportAction,
+ transaction,
+ transactionViolations,
+ );
+ }
return {
- // Shared base fields — spread directly into any updateMoneyRequest* call
transactionID,
- transactionThreadReport,
- parentReport,
+ transactionThreadReport: resolvedTransactionThreadReport,
+ parentReport: resolvedParentReport,
policy,
policyCategories,
parentReportNextStep,There was a problem hiding this comment.
@mohammadjafarinejad, it looks great. Let’s implement it.
There was a problem hiding this comment.
I’ll apply it locally and start full testing while waiting for your push. Please confirm that it fixes the issue without break the current progress.
There was a problem hiding this comment.
Cool, I'll start testing as well. I'll let you know once I finish testing.
Reviewer Checklist
Screenshots/VideosAndroid: HybridAppScreen.Recording.2026-05-07.at.4.03.45.AM.movAndroid: mWeb ChromeScreen.Recording.2026-05-07.at.4.05.48.AM.moviOS: HybridAppScreen.Recording.2026-05-07.at.3.57.14.AM.moviOS: mWeb SafariScreen.Recording.2026-05-07.at.4.03.08.AM.movMacOS: Chrome / SafariScreen.Recording.2026-05-07.at.1.44.13.AM.mov |
|
This Issue already exists on staging, and not related to our PR Screen.Recording.2026-05-07.at.1.44.13.AM.mov |
|
@ahmedGaber93 are you done with your review? Thanks! |
|
Yes, @mohammadjafarinejad just the above 2 comment #89166 (review) |
…port action resolution
|
@ahmedGaber93 Ready for re-review. |
|
@mohammadjafarinejad There’s a new bug where updating the category causes the new value to flicker twice. Steps to reproduce:
This looks like a race condition when edit quickly on a slow network connection. Maybe not related to latest changes. Screen.Recording.2026-05-11.at.12.24.17.PM.movScreen.Recording.2026-05-11.at.1.00.52.PM.mov |
|
@mohammadjafarinejad I finished my review, tested well and works as expected, except for the issue mentioned above. |
|
@ahmedGaber93 This is my guess, WDYT? We are applying two updates on a slow network, and both updates are happening sequentially at same time:
The server receives the Then the server receives the |
|
@mohammadjafarinejad This maybe what happened on the second video, but not sure if this the case on the first video Are you able to reproduce the case on the first video? |
|
@ahmedGaber93 Yes, i can reprduce both of them (in slow network only), I think the first video is likely the same issue, but on the same field:
On a slow network, the first response can arrive after the UI already shows C, but that older response still contains B, so the UI briefly reverts from C -> B. Then the second response arrives and changes it back from B -> C. |
|
Ah, I think this may also be caused by the side-effect After the first edit, once Then, after the second Screen.Recording.2026-05-12.at.9.40.58.AM.mov |
follow-ups if they not blockers
|
|
@mohammadjafarinejad I think we should mention in the QA steps that QA needs to follow the steps from the blocker issues (or please add them here), except for the issues that will be fixed in follow-up PRs: #88681 and #88659, for better clarification. |
ahmedGaber93
left a comment
There was a problem hiding this comment.
LGTM!
Follow-ups if they not blockers #89166 (comment)
puneetlath
left a comment
There was a problem hiding this comment.
Wow, behemoth of a PR. Was a tough review. I left a couple of minor comments.
| shouldDisplayBelowModals?: boolean; | ||
|
|
||
| /** | ||
| * Whether a RIGHT_DOCKED modal should keep its backdrop when rendered in a narrow pane context. |
There was a problem hiding this comment.
Can we give a little more detail in this comment itself on what the considerations are for why this would be true or false.
| * @returns The string with all line breaks removed | ||
| */ | ||
| function removeLineBreaks(text = '') { | ||
| return text.replaceAll(CONST.REGEX.LINE_BREAK, ''); |
There was a problem hiding this comment.
Should the line breaks not be spaces? Like would this turn this:
this is line one
this is line two
Into this:
this is line onethis is line two
@ahmedGaber93 are you asking whether these should be blockers? |
@puneetlath yes, WDYT? |
This reverts commit 78be79e.
Explanation of Change
This PR reapplies #83127 (inline editing for tables on desktop), which was reverted in #88751, with the original issues fixed and additional improvements.
Fixed Issues
$ #82534
$ #88711
PROPOSAL: #82534 (comment)
Tests
Offline tests
QA Steps
Same as tests.
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectiontoggleReportand notonIconClick)src/languages/*files and using the translation methodSTYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.ScrollViewcomponent to make it scrollable when more elements are added to the page.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.